/*
 * Copyright (c) 2011-2022, baomidou (jobob@qq.com).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.baomidou.mybatisplus.advance.injector.processor;

import com.baomidou.mybatisplus.advance.injector.ASTBuilder;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Names;

/**
 * 生成数据库查询相关方法
 *
 * @author wanglei
 * @since 2022-03-15
 */
public class GenQueryMethodProcessor implements MybatisPlusAPTProcessor {

    @Override
    public void generate(ASTBuilder astBuilder) {
        if (!astBuilder.hasMethod("bean2Wrapper")) {
            astBuilder.addMethod(genBean2WrapperMethod(astBuilder));
        }
        if (!astBuilder.hasMethod("count")) {
            astBuilder.addMethod(genCountMethod(astBuilder));
        }
        if (!astBuilder.hasMethod("update")) {
            astBuilder.addMethod(genUpdateMethod(astBuilder));
        }
        if (!astBuilder.hasMethod("delete")) {
            astBuilder.addMethod(genDeleteMethod(astBuilder));
        }
        if (!astBuilder.hasMethod("list")) {
            astBuilder.addMethod(genListMethod(astBuilder));
        }
        if (!astBuilder.hasMethod("one")) {
            astBuilder.addMethod(genOneMethod(astBuilder));
        }
    }




    /**
     * 根据设置的参数查询集合
     *
     * @param astBuilder
     * @return
     */
    public JCTree.JCMethodDecl genListMethod(ASTBuilder astBuilder) {
        return genDBOperatorMethod(astBuilder, "java.util.List", "com.baomidou.mybatisplus.advance.injector.FuntionTools.list", new String[]{
                "java.util.List", "java.lang.Object",
        }, false,  "list");
    }



    /**
     * 根据条件查询单个对象
     *
     * @param astBuilder
     * @return
     */
    public JCTree.JCMethodDecl genOneMethod(ASTBuilder astBuilder) {
        return genDBOperatorMethod(astBuilder, astBuilder.getJcClassDecl().getSimpleName().toString(), "com.baomidou.mybatisplus.advance.injector.FuntionTools.one", new String[]{
                "java.util.List", "java.lang.Object"
        }, true,  "one");
    }

    /**
     * 根据条件查询数量
     *
     * @param astBuilder
     * @return
     */
    public JCTree.JCMethodDecl genCountMethod(ASTBuilder astBuilder) {
        return genDBOperatorMethod(astBuilder, "java.lang.Long", "com.baomidou.mybatisplus.advance.injector.FuntionTools.count", new String[]{
                "java.util.List", "java.lang.Object"
        }, false,  "count");
    }

    /**
     * 根据条件更新，po里有哪些字段只更新哪些字段 返回受影响行数
     *
     * @param astBuilder
     * @return
     */
    public JCTree.JCMethodDecl genUpdateMethod(ASTBuilder astBuilder) {
        return genDBOperatorMethod(astBuilder, "java.lang.Integer", "com.baomidou.mybatisplus.advance.injector.FuntionTools.update", new String[]{
                "java.util.List", "java.lang.Object"
        }, false,  "update");
    }

    /**
     * 根据条件删除返回受影响行数
     *
     * @param astBuilder
     * @return
     */
    public JCTree.JCMethodDecl genDeleteMethod(ASTBuilder astBuilder) {
        return genDBOperatorMethod(astBuilder, "java.lang.Integer", "com.baomidou.mybatisplus.advance.injector.FuntionTools.delete", new String[]{
                "java.util.List", "java.lang.Object"
        }, false,  "delete");
    }


    /**
     * 生成bean2Wrapper方法
     *
     * @param astBuilder ast构造器
     * @return
     */
    private JCTree.JCMethodDecl genBean2WrapperMethod(ASTBuilder astBuilder) {
        return genDBOperatorMethod( astBuilder, "com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper",
                "com.baomidou.mybatisplus.advance.injector.FuntionTools.buildQueryWrapper",
                new String[]{"java.util.List","java.lang.Object"},  false, "bean2Wrapper");
    }

    /**
     * 生成数据库增删改查方法
     *
     * @param astBuilder
     * @param execMethodReturnType 返回值类型
     * @param execMethodName       执行方法名
     * @param execMethodParamTypes 参数类型
     * @param isOne                是否返回一个对象(比如 返回一个Student)
     * @param methodName           方法名
     * @return
     */
    public JCTree.JCMethodDecl genDBOperatorMethod(ASTBuilder astBuilder, String execMethodReturnType, String execMethodName, String[] execMethodParamTypes,  boolean isOne, String methodName) {
        Names names = astBuilder.getNames();
        TreeMaker treeMaker = astBuilder.getTreeMaker();
        JCTree.JCClassDecl jcClassDecl = astBuilder.getJcClassDecl();
        // 生成 调用方法的参数列表
        List<JCTree.JCExpression> execFunparamsList = List.nil();
        // 参数添加this.wrapperVars
        execFunparamsList = execFunparamsList.append(treeMaker.Select(
                treeMaker.Ident(names.fromString("this"))
                , names.fromString("actions")
        ));

        // po
        execFunparamsList = execFunparamsList.append(treeMaker.Ident(names.fromString("this")));

        ListBuffer<JCTree.JCStatement> statments = new ListBuffer<JCTree.JCStatement>();
        //不是一个PO 的时候不需要强制转换部分的代码
        if (!isOne) {
            //方法参数: 方法的形式参数
            JCTree.JCVariableDecl result = treeMaker.VarDef(
                    treeMaker.Modifiers(Flags.PARAMETER, List.nil()),  //访问变量标识符
                    names.fromString("result"),        //变量名
                    astBuilder.typeRef(execMethodReturnType),     //变量类型
                    astBuilder.callMethod(execMethodName,
                            execMethodParamTypes, execFunparamsList));     //变量初始值
            //生成return语句: return result;
            JCTree.JCReturn returnStatement = treeMaker.Return(treeMaker.Ident(names.fromString("result")));

            statments = statments.append(result);
            statments = statments.append(returnStatement);
        } else {
            //是一个PO的时候需要强制转换
            //方法参数: 方法的形式参数
            JCTree.JCVariableDecl tempObj = treeMaker.VarDef(
                    treeMaker.Modifiers(Flags.PARAMETER, List.nil()),  //访问变量标识符
                    names.fromString("tempObj"),        //变量名
                    astBuilder.typeRef("java.lang.Object"),     //变量类型
                    astBuilder.callMethod(execMethodName,
                            execMethodParamTypes, execFunparamsList));     //变量初始值
            //无视这段重复把-- 就当你没看到
            JCTree.JCVariableDecl result = treeMaker.VarDef(
                    treeMaker.Modifiers(Flags.PARAMETER, List.nil()),  //访问变量标识符
                    names.fromString("result"),        //变量名
                    astBuilder.typeRef(jcClassDecl.sym.toString()),     //变量类型
                    treeMaker.Ident(names.fromString("tempObj")));     //变量初始值

            //添加一个强制转换
            result.init = treeMaker.TypeCast(astBuilder.typeRef(jcClassDecl.sym.toString()), treeMaker.Ident(names.fromString("tempObj")));

            //生成return语句: return result;
            JCTree.JCReturn returnStatement = treeMaker.Return(treeMaker.Ident(names.fromString("result")));

            statments = statments.append(tempObj);
            statments = statments.append(result);
            statments = statments.append(returnStatement);
        }
        //生成方法体
        JCTree.JCBlock body = treeMaker.Block(0, statments.toList());
        //方法访问修饰符: public
        JCTree.JCModifiers modifiers = treeMaker.Modifiers(Flags.PUBLIC);
        List<JCTree.JCVariableDecl> methodParams = List.nil();

        JCTree.JCMethodDecl methodDecl = treeMaker.MethodDef(
                modifiers,
                names.fromString(methodName),
                astBuilder.typeRef(execMethodReturnType),
                List.nil(),
                methodParams,
                List.nil(),
                body,
                null
        );
        return methodDecl;
    }
}
